package com.ruoyi.quartz.task;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.binding.domain.JDP.Tools.OkHttpSessionClient;
import com.ruoyi.binding.domain.MhBinding;
import com.ruoyi.binding.domain.MhOrderAllocate;
import com.ruoyi.binding.domain.MhOrderRemote;
import com.ruoyi.binding.domain.MhUserGameDispatchConfig;
import com.ruoyi.binding.service.IMhBindingService;
import com.ruoyi.binding.service.IMhOrderAllocateService;
import com.ruoyi.binding.service.IMhOrderRemoteService;
import com.ruoyi.binding.service.IMhUserGameDispatchConfigService;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.job.TaskException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.quartz.domain.SysJob;
import com.ruoyi.quartz.service.ISysJobService;
import com.ruoyi.quartz.util.CronUtils;
import com.ruoyi.quartz.util.ScheduleUtils;
import com.ruoyi.system.service.ISysUserService;
import okhttp3.OkHttpClient;
import okhttp3.Response;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Component
public class MhAutoDispatch {
    private static final Logger log = LoggerFactory.getLogger(MhAutoDispatch.class);
    @Autowired
    private IMhUserGameDispatchConfigService mhUserGameDispatchConfigService;
    @Autowired
    private ISysJobService jobService;
    @Autowired
    private ISysUserService userService;
    @Autowired
    private IMhOrderAllocateService mhOrderAllocateService;
    @Autowired
    private IMhOrderRemoteService mhOrderRemoteService;
    @Autowired
    private IMhBindingService mhBindingService;
    @Autowired
    private OkHttpSessionClient sessionClient;

    // 自动同步所有开启“自动派单”的用户配置到 Quartz 定时任务
    // 职责：
    // 1) 拉取开启自动派单的配置
    // 2) 对每个配置创建/更新对应的 Quartz 任务
    // 3) 清理已存在但已被关闭或删除配置的“孤儿任务”
    public void autoScan() throws SchedulerException, TaskException {
        System.out.println("自动派单任务执行中");
        // 获取所有开启自动派单的用户
        List<MhUserGameDispatchConfig> autoDispatchUsers = mhUserGameDispatchConfigService.selectAllAutoDispatch();
        log.info("派单任务同步执行中,数量: {}", autoDispatchUsers.size());
        if (autoDispatchUsers.isEmpty()) {
            log.info("没有开启自动派单的用户");
            return;
        }
        for (MhUserGameDispatchConfig config : autoDispatchUsers) {
            // 在 Quartz 中添加或更新任务（若存在则更新，不存在则创建）
            addOrUpdateJob(mhConfigToSysJob(config));
        }
        // 删除未配置的任务
        List<SysJob> existingJobs = jobService.selectJobList(new SysJob());
        for (SysJob job : existingJobs) {
            if ("AUTO".equals(job.getJobGroup())) {
                boolean found = false;
                for (MhUserGameDispatchConfig config : autoDispatchUsers) {
                    if (Objects.equals(config.getTaskId(), job.getJobId())) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    // 清理掉已经不存在于配置表中的自动派单任务，避免脏任务残留
                    log.info("删除未配置的自动派单任务,任务ID: {}", job.getJobId());
                    jobService.deleteJobByIds(new Long[]{job.getJobId()});
                }
            }
        }
        log.info("自动派单任务同步执行完成");
    }

    // 单个任务的派单执行入口（由 Quartz 触发）
    // 流程：
    // 1) 根据 taskId 查询派单配置
    // 2) 组装订单查询条件（按游戏名、部门）拉取待派单订单
    // 3) 校验用户绑定信息（用于计算抽成等）
    // 4) 将订单分批组装消息内容，并轮询发送到企业微信/自定义 webhook
    public void autoDispatch(String taskId) throws ServiceException {
        log.info("自动派单任务执行中{}", taskId);
        MhUserGameDispatchConfig mhDispatch = mhUserGameDispatchConfigService
                .selectMhUserGameDispatchConfigByTaskId(Long.valueOf(taskId));
        if (mhDispatch == null) {
            log.error("自动派单任务配置不存在，taskId: {}", taskId);
        }

//      在部门表中查询用户所在部门的dispatchMode字段
        String dispatchMode = userService.selectUserById(mhDispatch.getUserId()).getDept().getDispatchMode();
        if (StringUtils.isEmpty(dispatchMode)) {
            log.error("用户所在部门未配置派单模式，无法执行自动派单任务，taskId: {}", taskId);
            throw new ServiceException("用户所在部门未配置派单模式");
        }
        log.info("当前部门dispatchMode: {}", dispatchMode);
        if (dispatchMode.equals("1")) {
            log.info("部门dispatchMode为1，使用本地发送");
            localSend(taskId, mhDispatch);
        } else if (dispatchMode.equals("0")) {
            log.info("部门dispatchMode为0，使用JDP发送");
            remoteSend(taskId, mhDispatch);
        } else {
            log.error("用户所在部门dispatchMode配置错误，无法执行自动派单任务，taskId: {}", taskId);
            throw new ServiceException("用户所在部门派单模式配置错误，联系管理员");
        }


    }

    private void remoteSend(String taskId, MhUserGameDispatchConfig mhDispatch) {
        MhOrderRemote query = new MhOrderRemote();
        if (mhDispatch != null) {
            query.setGameName(mhDispatch.getGameName());
        }
        // 以用户所在部门为维度筛选可见订单
        query.setDeptId(userService.selectUserById(mhDispatch.getUserId()).getDeptId());
        log.info("自动派单任务查询订单列表,游戏名: {}, 部门ID: {}", mhDispatch.getGameName(), query.getDeptId());

        List<MhOrderRemote> orderList = mhOrderRemoteService.selectMhOrderRemoteList(query);
        if (orderList.isEmpty()) {
            log.info("自动派单任务{}没有查询到订单", taskId);
            throw new ServiceException("自动派单任务没有查询到订单");
        }
        MhBinding mhBinding = mhBindingService.selectMhBindingByUserId(mhDispatch.getUserId());
        if (mhBinding == null) {
            throw new ServiceException("当前用户未绑定账号，请先联系管理员绑定账号");
        }
        log.info("当前用户绑定信息：{}", mhBinding);

        // 按批次发送，避免 webhook 单次消息过长或触发限流
        int batchSize = 8;
        // 分批发送到 webhook
        int totalBatches = orderList.size() / batchSize + (orderList.size() % batchSize == 0 ? 0 : 1);
        String[] webhooks = parseDbArrayString(
                userService.selectUserById(mhDispatch.getUserId()).getBotUrls().toString());
        for (int batchNum = 0; batchNum < totalBatches; batchNum++) {
            int start = batchNum * batchSize;
            int end = Math.min((batchNum + 1) * batchSize, orderList.size());

            List<MhOrderRemote> batch = orderList.subList(start, end);

            StringBuilder msgContent = new StringBuilder();
            for (int i = 0; i < batch.size(); i++) {

                // 先转字符串再转 BigDecimal，兼容 int/long/double 等类型
                // 计算抽成后的最终价格（含渠道、个人等维度的抽成规则）
                BigDecimal recFee = mhOrderAllocateService.calculateFinalFee(mhBinding, mhDispatch.getUserId(),
                        mhDispatch.getGameName(), String.valueOf(batch.get(i).getRecFee()));
                if (recFee == null) {
                    log.error("订单 recFee 为空，无法计算抽成价格");
                    continue;
                }
                log.info("订单原价 recFee={}，最终价格 finalPrice={}", batch.get(i).getPrice(), recFee);
                if (Objects.equals(mhDispatch.getMsgType(), "markdown")) {
                    msgContent.append("**订单号**:<font color=\"comment\">").append(batch.get(i).getOrderIdView())
                            .append("</font>\n")
                            .append("**游戏名**:<font color=\"info\">").append(batch.get(i).getGameName())
                            .append("</font>\n")
                            .append("**发单价**:<font color=\"warning\">").append(recFee).append("</font>\n")
                            .append("**任务内容**:").append(batch.get(i).getProject())
                            .append("\n----------------------\n\n");
                } else if (Objects.equals(mhDispatch.getMsgType(), "text")) {
                    msgContent.append("订单号:").append(batch.get(i).getOrderIdView()).append("\n")
                            .append("游戏名:").append(batch.get(i).getGameName()).append("\n")
                            .append("发单价:").append(recFee).append("\n")
                            .append("任务内容:").append(batch.get(i).getProject()).append("\n----------------------\n\n");
                }
            }

            JSONObject wxMsg = new JSONObject()
                    .fluentPut("msgtype", mhDispatch.getMsgType())
                    .fluentPut(mhDispatch.getMsgType(), new JSONObject().fluentPut("content", msgContent));

            // 轮询选择 webhook，下发多群/多机器人
            String webhookUrl = webhooks[batchNum % webhooks.length].trim();
            try (Response wxResp = sessionClient.simplePostJson(webhookUrl, wxMsg)) {
                String responseStr = wxResp.body() != null ? wxResp.body().string() : "无返回内容";
                log.info("任务ID={} 批次 {}/{} 已发送到webhook={} 响应:{}", taskId, batchNum + 1, totalBatches, webhookUrl,
                        responseStr);
            } catch (IOException e) {
                log.error("任务ID={} 发送webhook异常", taskId, e);
            }
        }

    }

    private void localSend(String taskId, MhUserGameDispatchConfig mhDispatch) {
        MhOrderAllocate query = new MhOrderAllocate();
        if (mhDispatch != null) {
            query.setGameName(mhDispatch.getGameName());
        }

        // 以用户所在部门为维度筛选可见订单
        query.setDeptId(userService.selectUserById(mhDispatch.getUserId()).getDeptId());
        log.info("自动派单任务查询订单列表,游戏名: {}, 部门ID: {}", mhDispatch.getGameName(), query.getDeptId());
        List<MhOrderAllocate> orderList = mhOrderAllocateService.selectMhOrderAllocateList(query);
        if (orderList.isEmpty()) {
            log.info("自动派单任务{}没有查询到订单", taskId);
            throw new ServiceException("自动派单任务没有查询到订单");
        }

        MhBinding mhBinding = mhBindingService.selectMhBindingByUserId(mhDispatch.getUserId());
        if (mhBinding == null) {
            throw new ServiceException("当前用户未绑定账号，请先联系管理员绑定账号");
        }
        log.info("当前用户绑定信息：{}", mhBinding);

        // 按批次发送，避免 webhook 单次消息过长或触发限流
        int batchSize = 8;
        // 分批发送到 webhook
        int totalBatches = orderList.size() / batchSize + (orderList.size() % batchSize == 0 ? 0 : 1);
        String[] webhooks = parseDbArrayString(
                userService.selectUserById(mhDispatch.getUserId()).getBotUrls().toString());
        for (int batchNum = 0; batchNum < totalBatches; batchNum++) {
            int start = batchNum * batchSize;
            int end = Math.min((batchNum + 1) * batchSize, orderList.size());

            List<MhOrderAllocate> batch = orderList.subList(start, end);

            StringBuilder msgContent = new StringBuilder();
            for (int i = 0; i < batch.size(); i++) {

                // 先转字符串再转 BigDecimal，兼容 int/long/double 等类型
                // 计算抽成后的最终价格（含渠道、个人等维度的抽成规则）
                BigDecimal recFee = mhOrderAllocateService.calculateFinalFee(mhBinding, mhDispatch.getUserId(),
                        mhDispatch.getGameName(), String.valueOf(batch.get(i).getPrice()));
                if (recFee == null) {
                    log.error("订单 recFee 为空，无法计算抽成价格");
                    continue;
                }
                log.info("订单原价 recFee={}，最终价格 finalPrice={}", batch.get(i).getPrice(), recFee);
                if (Objects.equals(mhDispatch.getMsgType(), "markdown")) {
                    msgContent.append("**订单号**:<font color=\"comment\">").append(batch.get(i).getOrderIdView())
                            .append("</font>\n")
                            .append("**游戏名**:<font color=\"info\">").append(batch.get(i).getGameName())
                            .append("</font>\n")
                            .append("**发单价**:<font color=\"warning\">").append(recFee).append("</font>\n")
                            .append("**任务内容**:").append(batch.get(i).getProject())
                            .append("\n----------------------\n\n");
                } else if (Objects.equals(mhDispatch.getMsgType(), "text")) {
                    msgContent.append("订单号:").append(batch.get(i).getOrderIdView()).append("\n")
                            .append("游戏名:").append(batch.get(i).getGameName()).append("\n")
                            .append("发单价:").append(recFee).append("\n")
                            .append("任务内容:").append(batch.get(i).getProject()).append("\n----------------------\n\n");
                }
            }

            JSONObject wxMsg = new JSONObject()
                    .fluentPut("msgtype", mhDispatch.getMsgType())
                    .fluentPut(mhDispatch.getMsgType(), new JSONObject().fluentPut("content", msgContent));

            // 轮询选择 webhook，下发多群/多机器人
            String webhookUrl = webhooks[batchNum % webhooks.length].trim();
            try (Response wxResp = sessionClient.simplePostJson(webhookUrl, wxMsg)) {
                String responseStr = wxResp.body() != null ? wxResp.body().string() : "无返回内容";
                log.info("任务ID={} 批次 {}/{} 已发送到webhook={} 响应:{}", taskId, batchNum + 1, totalBatches, webhookUrl,
                        responseStr);
            } catch (IOException e) {
                log.error("任务ID={} 发送webhook异常", taskId, e);
            }
        }
    }


    // 将数据库中的数组字符串（如：["url1","url2"] 或 [url1, url2]）解析为字符串数组
    private String[] parseDbArrayString(String dbValue) {
        if (dbValue == null || dbValue.trim().isEmpty()) {
            return new String[0];
        }
        // 去掉两边的方括号
        String cleaned = dbValue.trim();
        if (cleaned.startsWith("[") && cleaned.endsWith("]")) {
            cleaned = cleaned.substring(1, cleaned.length() - 1);
        }
        // 如果去掉方括号后是空字符串，直接返回空数组
        if (cleaned.isEmpty()) {
            return new String[0];
        }
        // 按逗号分割，并去掉首尾空格
        String[] parts = cleaned.split("\\s*,\\s*");
        return parts;
    }

    // 按是否存在决定新增或更新任务；若仅状态变化则切换状态，若 Cron 有变则更新任务
    public void addOrUpdateJob(SysJob job) throws SchedulerException, TaskException, ServiceException {
        String error = jobValidate(job);
        if (StringUtils.isNotEmpty(error)) {
            log.error(error);
            throw new ServiceException(error);
        }

        SysJob exist = null;
        if (job.getJobId() != null) {
            exist = jobService.selectJobById(job.getJobId());
        }

        if (exist != null) {
            // 更新已有任务
            // 判断时间是否改变，改变了走 updateJob，否则走 changeStatus
            if (!job.getCronExpression().equals(exist.getCronExpression())) {
                jobService.updateJob(job);
            } else {
                jobService.changeStatus(job);
            }
        } else {
            // 新增任务
            jobService.insertJob(job);
        }
    }

    // 任务合法性校验：
    // 1) Cron 表达式
    // 2) 调用目标不包含危险协议（rmi/ldap/http(s)等）
    // 3) 调用目标在白名单内
    private String jobValidate(SysJob job) {
        if (!CronUtils.isValid(job.getCronExpression())) {
            return "新增任务'" + job.getJobName() + "失败Cron表达式不正确";
        } else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) {
            return "新增任务'" + job.getJobName() + "'失败，目标字符串不允许'rmi'调用";
        } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(),
                new String[]{Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS})) {
            return "新增任务'" + job.getJobName() + "'失败，目标字符串不允许'ldap(s)'调用";
        } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(),
                new String[]{Constants.HTTP, Constants.HTTPS})) {
            return "新增任务'" + job.getJobName() + "'失败，目标字符串存在违规";
        } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) {
            return "新增任务'" + job.getJobName() + "'失败，目标字符串存在违规";
        } else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) {
            return "新增任务'" + job.getJobName() + "'失败，目标字符串不在白名单内";
        }
        return null;
    }

    // 将秒级间隔转换为 Cron 表达式（仅支持 < 24h 的常见场景）
    public String secondsToCron(long totalSeconds) {
        if (totalSeconds <= 0) {
            throw new IllegalArgumentException("间隔秒数必须大于 0");
        }
        if (totalSeconds > 24 * 60 * 60) {
            throw new IllegalArgumentException("不支持超过 24 小时的间隔");
        }

        if (totalSeconds < 60) {
            // 每 totalSeconds 秒
            return String.format("0/%d * * * * ?", totalSeconds);
        } else if (totalSeconds % 60 == 0 && totalSeconds < 3600) {
            // 每 N 分钟
            return String.format("0 0/%d * * * ?", totalSeconds / 60);
        } else if (totalSeconds % 3600 == 0) {
            // 每 N 小时
            return String.format("0 0 0/%d * * ?", totalSeconds / 3600);
        } else {
            throw new IllegalArgumentException("Cron 无法精确表达该间隔，请用 SimpleTrigger");
        }
    }

    // 将自动派单配置转换为 Quartz 的 SysJob 实体
    // 约定：
    // - jobGroup 固定为 "AUTO"，便于统一管理与清理
    // - invokeTarget 固定指向本类 autoDispatch，并传入 taskId
    private SysJob mhConfigToSysJob(MhUserGameDispatchConfig config) {
        SysJob job = new SysJob();
        job.setJobId(config.getTaskId());
        job.setJobName(
                "自动派单任务-" + userService.selectUserById(config.getUserId()).getUserName() + "-" + config.getGameName());
        job.setJobGroup("AUTO");
        job.setInvokeTarget("mhAutoDispatch.autoDispatch('" + config.getTaskId() + "')");
        job.setCronExpression(secondsToCron(config.getDispatchInterval()));
        job.setStatus(config.getAutoDispatch() == 1 ? "0" : "1"); // 0:启用, 1:禁用
        job.setMisfirePolicy("3"); // 3:放弃执行
        job.setConcurrent("0"); // 0:允许, 1:不允许
        job.setCreateBy(userService.selectUserById(config.getUserId()).getUserName());
        return job;
    }
}
